classdef LiraNeuralNetwork < handle
    %LIRANEURALNETWORK Summary of this class goes here
    %   Detailed explanation goes here
    
    properties
        featureLayer
        targets
        weights
        labels
        defenseSpace
        incrementValue
    end
    
    % Constructors
    methods
        function obj=LiraNeuralNetwork(varargin) 
            % features, inputs, targets, maxEpochs, resumeTraining,
            % resumeWorkspace
            obj.featureLayer = varargin{1};
            obj.defenseSpace = varargin{2};
            obj.incrementValue = varargin{3};
            inputs = varargin{4};
            obj.targets = varargin{5};
            maxEpochs = varargin{6};
            resumeTraining = varargin{7};
            resumeWorkspace = varargin{8};
            
            obj.labels = unique(obj.targets);
            
            if (resumeTraining == 1)
                load(resumeWorkspace + '.mat','liraNN');
                obj = liraNN;
            end
            obj.train(inputs, maxEpochs, resumeTraining, resumeWorkspace);
        end
        
        function train(obj, inputs, maxEpochs, resumeTraining,...
                resumeWorkspace)
            featureLayerSize = length(obj.featureLayer);
            outputLayerSize = length(obj.labels);
            obj.weights = zeros(outputLayerSize,...
                length(obj.featureLayer), 'single');
            
            trainingResumeFile = [resumeWorkspace,'_train'];
            
            inputsLength = length(inputs);
            if (resumeTraining == 0)
                disp('Calculating A layer outputs...');
                aLayerStart = tic;
                aOutput = cell(1,inputsLength);
                for inputIndex=1:inputsLength
                    fprintf('Creating feature: %d/%d\n',inputIndex,...
                        inputsLength);
                    % A layer activity
                    aOutputForImage = zeros(1, featureLayerSize, 'single');
                    
                    inputSliced = inputs{inputIndex};
                    featureLayerSliced = obj.featureLayer;
                    for featureIndex=1:featureLayerSize
                        aOutputForImage(featureIndex) = ...
                            featureLayerSliced(featureIndex)...
                            .hasFeature(inputSliced);

                    end
                    aOutput{inputIndex} = aOutputForImage;
                end
                toc(aLayerStart);
                disp('Saving workspace...');
                save(trainingResumeFile,'obj','maxEpochs','aOutput');
            else
                disp('Loading workspace...');
                load([trainingResumeFile,'.mat'],'maxEpochs','aOutput');
            end
            
            totalEpochTime = 0;
            for epoch=1:maxEpochs
                converged = 1;
                fprintf('Epoch: %d\n', epoch);
                epochStart = tic;

                rActivity = zeros(1, outputLayerSize, 'single');
                
                for inputIndex=1:inputsLength
                    % A layer activity
                    aOutputForImage = aOutput{inputIndex};
                    
                    % R layer activity
                    for i=1:outputLayerSize
                        rActivity(i) =...
                            sum(obj.weights(i,:).*aOutputForImage);
                    end
                    
                    % Update Rm with Rm*
                    expectedLabel = obj.targets(inputIndex);
                    expectedRIndex = find(obj.labels==expectedLabel);
                    rActivity(expectedRIndex) =...
                        rActivity(expectedRIndex)*(1-obj.defenseSpace);
                    
                    % Winner selection
                    [~, chosenRIndex] = max(rActivity);

                    % Weight adjustment
                    if (expectedRIndex == chosenRIndex)
                        %do nothing
                    else
                        converged = false;
                        activeIndexes = find(aOutputForImage > 0);
                        for i=1:length(activeIndexes)
                            obj.weights(expectedRIndex,...
                                activeIndexes(i))=...
                                obj.weights(expectedRIndex,...
                                activeIndexes(i))+...
                                obj.incrementValue;
                            obj.weights(chosenRIndex,...
                                activeIndexes(i))=...
                                obj.weights(chosenRIndex,...
                                activeIndexes(i))-...
                                obj.incrementValue;
                        end
                    end
                end
                totalEpochTime = toc(epochStart) + totalEpochTime;
                if (converged)
                    fprintf('Converged after epoch %d (%ds / epoch)\n',...
                        (epoch-1), totalEpochTime/(epoch-1));
                    break;
                end
            end
            disp('Finished training process');
        end
        
        function [resultSet, meanClassificationTime] = ...
                classify(obj, inputs)
            featureLayerSize = length(obj.featureLayer);
            outputLayerSize = length(obj.labels);
            aActivity = zeros(1, featureLayerSize, 'single');
            rActivity = zeros(1, outputLayerSize, 'single');
            
            resultSet = zeros(1, length(inputs), 'uint8');
            
            classificationTimeStart = tic;
            inputsLength = length(inputs);
            for inputIndex=1:inputsLength
                for featureIndex=1:featureLayerSize
                    aActivity(featureIndex) = ...
                        obj.featureLayer(featureIndex)...
                        .hasFeature(inputs{inputIndex});
                end
                
                for i=1:outputLayerSize
                    rActivity(i) = sum(obj.weights(i,:).*aActivity);
                end
                
                % Selecting winner
                [~, chosenRIndex] = max(rActivity);

                resultSet(inputIndex) = obj.labels(chosenRIndex);
            end
            meanClassificationTime = ...
                toc(classificationTimeStart)/inputsLength;
        end
    end
end